winbrew_engines\windows\exe/
install.rs

1use anyhow::{Context, Result, bail};
2use std::fs;
3use std::path::Path;
4use std::process::Command;
5use tracing::warn;
6
7use crate::models::catalog::package::CatalogInstaller;
8use crate::models::install::engine::{EngineInstallReceipt, EngineKind, EngineMetadata};
9
10use super::NATIVE_EXE_SUCCESS_EXIT_CODES;
11use super::metadata::{NativeExeInstallMetadata, capture_native_exe_metadata};
12use super::switches::build_install_args;
13use super::validation::validate_install_inputs;
14
15/// Install a native executable package by running the downloaded installer.
16///
17/// The installer family is expected to come from catalog metadata. The backend
18/// validates the inputs, builds family-specific switches, executes the installer
19/// process, and records uninstall metadata when Windows exposes it.
20pub(crate) fn install(
21    installer: &CatalogInstaller,
22    download_path: &Path,
23    install_dir: &Path,
24    package_name: &str,
25) -> Result<EngineInstallReceipt> {
26    validate_install_inputs(download_path, install_dir, package_name)?;
27
28    fs::create_dir_all(install_dir)
29        .with_context(|| format!("failed to create {}", install_dir.display()))?;
30
31    let args = build_install_args(installer, install_dir, package_name)?;
32
33    let status = Command::new(download_path)
34        .current_dir(download_path.parent().unwrap_or(Path::new(".")))
35        .args(&args)
36        .status()
37        .with_context(|| {
38            format!("failed to launch native executable installer for {package_name}")
39        })?;
40
41    let exit_code = status.code().ok_or_else(|| {
42        anyhow::anyhow!("native executable installer terminated without an exit code")
43    })?;
44
45    if !NATIVE_EXE_SUCCESS_EXIT_CODES.contains(&exit_code) {
46        bail!(
47            "native executable installer for {} failed with exit code {}",
48            package_name,
49            exit_code
50        );
51    }
52
53    let captured_metadata = capture_native_exe_metadata(package_name, install_dir);
54
55    if captured_metadata.is_none() {
56        warn!(
57            package = package_name,
58            install_dir = %install_dir.display(),
59            "native executable installer did not expose uninstall metadata"
60        );
61    }
62
63    let engine_metadata = captured_metadata.map(|metadata| match metadata {
64        NativeExeInstallMetadata::QuietOnly(command) => {
65            EngineMetadata::native_exe(Some(command), None)
66        }
67        NativeExeInstallMetadata::QuietAndStandard {
68            quiet_uninstall_command,
69            uninstall_command,
70        } => EngineMetadata::native_exe(Some(quiet_uninstall_command), Some(uninstall_command)),
71        NativeExeInstallMetadata::StandardOnly(command) => {
72            EngineMetadata::native_exe(None, Some(command))
73        }
74    });
75
76    Ok(EngineInstallReceipt::new(
77        EngineKind::NativeExe,
78        install_dir.to_string_lossy().into_owned(),
79        engine_metadata,
80    ))
81}